home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / exp < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  58.3 KB  |  1,680 lines

  1. #!/usr/local/bin/gawk -f
  2. # exp: expense tabulator
  3. # @(#) exp.gawk 3.6 95/10/08
  4. # 1989, 1990 John H. DuBois III (john@armory.com)
  5. # 90/05/19 Ported from DOS to XENIX
  6. # 91/07/19 Accept multiple input files
  7. # 91/08/04 Made width of name field a variable; set to 9 chars
  8. # 92/09/10 Extensive cleanup, added line splitting, change to #!awk
  9. #          script because gawk failed on it after cleanup
  10. # 92/12/09 Added help.
  11. # 93/06/25 Year may be included in dates.
  12. # 93/07/01 Added c option.
  13. # 93/07/02 Added use of HOUSELIB.
  14. # 94/04/24 Allow any separators except the one immediately before the 
  15. #          comment field to be spaces.  Fixed Sep assignment.
  16. # 94/06/11 Do not report on those who have 0 balance and are not in All.
  17. # 94/09/20 Ignore blank lines.
  18. # 95/03/31 Detect recursive aliases.  Ignore case in inmate names.
  19. # 95/04/15 Avoid timezone issues by working with days instead of seconds.
  20. #          Make p option work again.
  21. # 95/06/19 Added OwedBy and OwedTo vars
  22. # 95/07/12 Added dptTR options.
  23. # 95/07/24 Better checking of aliases.
  24. # 95/08/26 Make carryover output work correctly.  Reduced number of globals.
  25. # 95/09/30 Added P option.
  26. # 95/10/01 Changed d option to s; added eM options; fixed Inmates[] tests
  27. # 95/10/08 Added V option.
  28.  
  29. BEGIN {
  30.     Run()
  31. }
  32.  
  33. # Globals:
  34. # DefaultSep is true if the default field separator is being used.
  35. # MinReptBal is the minimum report balance.
  36. # Debug is true if debugging is on.
  37. # FS is the input field separator.
  38. # FILENAME is the name of the file currently being processed.
  39. # FNR is the line number last read from that file.
  40. # FirstDate and LastDate are the first and last dates encountered.
  41. # VBalances[Party,dir-sign "," cost-sign] is used for the -V option, to
  42. #   accumulate more detailed expense information.  Balances are added to
  43. #   VBalances[] just as they are added to Balances[], except that the absolute
  44. #   value of the cost fracftion is added, and additional sign indexes are used.
  45. #   dir-sign is 1 for expenses in which Party represents more
  46. #   of the owed-by field than the owed-to field, and -1 for expenses in which
  47. #   Party represents more of the owed-to field than than the owed by field. 
  48. #   cost-sign is 1 for expenses in which  the cost was positive, and -1 for
  49. #   expenses in which the cost was negative.
  50. function Run(  Lib,DefExp,Options,Carryover,PrintRunning,RunningBalance,
  51. LastPayment,PartyListGiven,ProcParties,StartDate,EndDate,MaxName,ArgInd,ret) {
  52.     Lib = "/usr/local/lib/house"
  53.     DefExp = "expenses"
  54.     MinReptBal = 0.02
  55.     DefaultSep = 1
  56.     if ((ARGC = ProcArgs(ARGC,ARGV,"bchrRltTxp:P:s:e:M(V",Options,1)) < 0) {
  57.     print "exp: " OptErr
  58.     exit(1)
  59.     }
  60.     if ("h" in Options) {
  61.     printf \
  62. "exp: process an expenses file.\n"\
  63. "Usage: exp [-bchlrRtTV] [-p<party,...>] [-P<payment-types>] [-M<min-amt>]\n"\
  64. "           [-s<start-date>] [-e<end-date>] [expenses-file ...]\n"\
  65. "If no expenses-file is given, the default filename \"%s\" is used.  It is\n"\
  66. "expected to be found in the directory %s.  If the environment\n"\
  67. "variable HOUSELIB is set, it is used instead of %s.  See the\n"\
  68. "exp(LOCAL) man page for a description of the input file format.  The only\n"\
  69. "users reported on are those who are currently in the \"All\" set and\n"\
  70. "those who have an outstanding balance whose absolute value is greater than\n"\
  71. "or equal to the minimum report balance, by default $%04.2f.  This can be\n"\
  72. "changed with the -M option.\n"\
  73. "Options: See the exp(LOCAL) man page.\n",DefExp,Lib,Lib,MinReptBal
  74.  
  75.     exit(0)
  76.     }
  77.     # If MakePTypes fails, it return an error message, else null
  78.     if ((PTypesGiven = "P" in Options) &&
  79.     ((e = MakePTypes(Options["P"],PTypes)) != ""))
  80.     ErrExit(e)
  81.     if ("HOUSELIB" in ENVIRON)
  82.     Lib = ENVIRON["HOUSELIB"]
  83.     DefExp = Lib "/" DefExp
  84.     Carryover = "c" in Options
  85.     PrintRunning = "r" in Options || "t" in Options || "R" in Options ||
  86.     "T" in Options
  87.     RunningBalance = "R" in Options || "T" in Options
  88.     LastPayment = "l" in Options
  89.     if (PartyListGiven = ("p" in Options))
  90.     NumPartiesGiven = MakeSet(ProcParties,tolower(Options["p"]),",")
  91.     if ("s" in Options)
  92.     if ((StartDate = date2day(Options["s"])) == -1)
  93.         ErrExit(Options["s"] ": Bad date.  Usage: -s yy/mm/dd",1)
  94.     if ("e" in Options)
  95.     if ((EndDate = date2day(Options["e"])) == -1)
  96.         ErrExit(Options["e"] ": Bad date.  Usage: -e yy/mm/dd",1)
  97.     if ("t" in Options || "T" in Options)
  98.     HeadTailInit()
  99.     if ("M" in Options)
  100.     MinReptBal = Options["M"]
  101.     Verbose = "V" in Options
  102.     Debug = "x" in Options
  103.     if (Debug) {
  104.     printf "Payment types:" > "/dev/stderr"
  105.     for (i in PTypes)
  106.         printf " " i > "/dev/stderr"
  107.     print "" > "/dev/stderr"
  108.     }
  109.     if (ARGC < 2) {
  110.     ARGV[1] = DefExp
  111.     ARGC = 2
  112.     }
  113.     FS = "\t+"
  114.     MaxName = 10
  115.     for (ArgInd = 1; ArgInd < ARGC; ArgInd++) {
  116.     FILENAME = ARGV[ArgInd]
  117.     FNR = 0
  118.     while ((ret = (getline < FILENAME)) == 1) {
  119.         FNR++
  120.         if (Debug)
  121.         printf "** Line %d: %s\n",FNR,$0 > "/dev/stderr"
  122.         if ($0 ~ "^(#|[ \t]*$)")
  123.         continue
  124.         if ($1 ~ /^[a-zA-Z0-9]+=/)    # Alias line w/o date
  125.         AliasLine(0,PrintRunning)
  126.         else {
  127.         UnixDays = GetDays($1)
  128.         if (UnixDays > LastDate)
  129.             LastDate = UnixDays
  130.         if (!FirstDate || UnixDays < FirstDate)
  131.             FirstDate = UnixDays
  132.         if ($2 ~ /^[a-zA-Z0-9]+=/) {    # Alias line w/ date
  133.             $1 = ""
  134.             AliasLine(UnixDays,PrintRunning)
  135.         }
  136.         else if ($2 == "Rates")
  137.             RatesLine()
  138.         else 
  139.             DataLine(UnixDays,Carryover,PrintRunning,RunningBalance,
  140.             LastPayment,PartyListGiven,ProcParties,StartDate,EndDate,
  141.             MaxName,PTypesGiven,PTypes)
  142.         }
  143.     }
  144.     if (ret)
  145.         ErrExit("Error reading file '" FILENAME "'.  Exiting.",1)
  146.     }
  147.     # Get time beforehand so that each strftime() will get the same time
  148. #    t = systime()
  149. #    CurMonth = (strftime("%Y",t)-1970)*12+strftime("%m",t)-1
  150. #    CurDay = month2day(CurMonth) + strftime("%d")
  151.  
  152.     if (Carryover)
  153.     ShowCarry()
  154.     else if (Verbose) {
  155.     # Give AllAll the final date + 1 so that it will add the span up to
  156.     # and including the final date
  157.     AddAll(InmateSet,LastAll,LastDate+1)
  158.     VerboseReport(MaxName,PartyListGiven,ProcParties)
  159.     }
  160.     else
  161.     Report("b" in Options,MaxName)
  162.     if (LastPayment)
  163.     ReportLastPayments(Inmates,Coord,ToMixed,LastPay,LastAmt)
  164.     TailFlush()
  165. }
  166.  
  167. # MakePTypes takes a payment types list as described in the help info and
  168. # converts it to a set for easy lookup.  Some translations are done to give
  169. # canonical forms:
  170. # N->bn,tp  P->bp,tn  pt->tp  pb->bp  nt->tn  nb->bn  b->bp,bn  t->tp,tn
  171. # In the canonical form, only letters from [pnbt] are allowed, and if two
  172. # letters are given, the letter that specifies the "owed field" (b or t) comes
  173. # first, and the letter that specifies the sign (p or n) comes last.  Thus,
  174. # the only elements allowed to exist in the set are (p,n,bp,tp,bn,tn)
  175. function MakePTypes(List,PTypes,  Elem,i,Type) {
  176.     split(List,Elem,",")
  177.     for (i in Elem) {
  178.     Type = Elem[i]
  179.     # Convert convenience names
  180.     if (Type == "N") {
  181.         PTypes["bn"]
  182.         PTypes["tp"]
  183.     }
  184.     else if (Type == "P") {
  185.         PTypes["bp"]
  186.         PTypes["tn"]
  187.     }
  188.     else if (Type == "b") {
  189.         PTypes["bp"]
  190.         PTypes["bn"]
  191.     }
  192.     else if (Type == "t") {
  193.         PTypes["tp"]
  194.         PTypes["tn"]
  195.     }
  196.     # Canonicalize letter order
  197.     else if (Type ~ /^[pn][tb]$/)
  198.         PTypes[substr(Type,2) substr(Type,1,1)]
  199.     else if (Type ~ /^([pn]|[tb][pn])$/)
  200.         PTypes[Type]
  201.     else
  202.         return Type ": Bad payment type.  Use -h for help."
  203.     }
  204.     return ""
  205. }
  206.  
  207. function dFmt(Amount) {
  208.     return sprintf("%.2f",Amount)
  209. }
  210.  
  211. function ReportLastPayments(Inmates,Coord,ToMixed,LastPay,LastAmt,
  212. PartyNum,Party) {
  213.     TailPrint("\nLast payments:")
  214.     for (PartyNum in Inmates) {
  215.     Party = Inmates[PartyNum]
  216.     if (Party != Coord)
  217.         TailPrint(sprintf("%-10s  %8s  %7s",ToMixed[Party],
  218.         Party in LastPay ? day2date(LastPay[Party]) : "NEVER",
  219.         Party in LastAmt ? "$" dFmt(LastAmt[Party]) : ""))
  220.     }
  221. }
  222.  
  223. # Rates line format:
  224. # date Rates rate-type party-name=amount ...
  225. function RatesLine() {
  226.     ;
  227.     #print "Rates line: " $0
  228. }
  229.  
  230. function AddAll(InmateSet,LastAll,UnixDays,  Inmate) {
  231.     for (Inmate in InmateSet) {
  232.     if (Debug)
  233.         printf "Adding %d days (%s to %s) to span for %s\n",
  234.         UnixDays - LastAll,day2date(LastAll),day2date(UnixDays),Inmate \
  235.         > "/dev/stderr"
  236.     # Add the span from the last All line through the current day - 1
  237.     PartySpans[Inmate] += UnixDays - LastAll
  238.     }
  239. }
  240.  
  241. # Globals: Year, Aliases, Inmates, InmateSet, Balances, Coord, ToMixed,
  242. # GlobalOwedTo, GlobalOwedBy, FS, DefaultSep
  243. # AliasLine: process an alias line.
  244. # If the line assigns a value to Year, the global Year is set to its value.
  245. # If the line assigns a value to Sep, the field separtor is set to its value.
  246. # Otherwise, the RHS of the assignment is put into the global Aliases[]
  247. # with an index of the LHS.
  248. # If the line assigns a value to All, the global Inmates[] is set to the
  249. # parties on its RHS (converted to lower case), they are added as indices
  250. # to the global Balances[], and the global InmateSet is set to them.
  251. # If the global Coord is not set and the alias is for All, Coord is set to
  252. # the first party on the RHS.
  253. # The global ToMixed[] is maintained as a map from the lowercase version
  254. # of each name (as used internally for indices) to the mixed-case version of
  255. # each name as it first appears in an All alias.
  256. # The global LastAll is used to keep track of the last time All was set,
  257. # for use by the verbose report.
  258. # Do lots of checking here since alias lines are relatively infrequent.
  259. function AliasLine(UnixDays,PrintRunning,  Fields,Name,Inmate,i) {
  260.     # Get rid of whitespace since it isn't needed/wanted on an alias line and
  261.     # is not obvious
  262.     if ($0 !~ /^[ \t]+sep[ \t]+=/)
  263.     gsub("[ \t]+","")
  264.     if (split($0,Fields,"=") != 2)
  265.     FileErrExit($0 ": bad alias.",1)
  266.     Name = tolower(Fields[1])
  267.     if (Name == "year") {
  268.     if ((Year = Fields[2]) !~ /^[0-9]+$/)
  269.         FileErrExit("Bad year.",1)
  270.     }
  271.     else if (Name == "owedto") {
  272.     if (Fields[2] != "")
  273.         ExpandList(Fields[2])    # Catch bad party here
  274.     GlobalOwedTo = Fields[2]
  275.     }
  276.     else if (Name == "owedby") {
  277.     if (Fields[2] != "")
  278.         ExpandList(Fields[2])    # Catch bad party here
  279.     GlobalOwedBy = Fields[2]
  280.     }
  281.     else if (Name == "sep") {
  282.     FS = Fields[2]
  283.     DefaultSep = 0
  284.     }
  285.     else {
  286.     if (Name == "")
  287.         FileErrExit("Empty alias name.",1)
  288.     # Allow empty aliases, so that e.g. All can be set to nothing to
  289.     # skip a period of time (relevant to the verbose option)
  290.     if (Fields[2] !~ /^([^-,]+,)?[^-,]+$|/)
  291.         FileErrExit("Bad value assigned to alias.",1)
  292.     if (Debug)
  293.         printf "Adding alias: %s=%s\n",Name,Fields[2] > "/dev/stderr"
  294.     Aliases[Name] = Fields[2]
  295.     if (Name == "all") {
  296.         if (Verbose) {
  297.         if (!UnixDays)
  298.             FileErrExit(\
  299.     "\n'All' alias lines must include a date to use the verbose option",1)
  300.         if (LastAll)
  301.             AddAll(InmateSet,LastAll,UnixDays)
  302.         LastAll = UnixDays
  303.         }
  304.         split(Fields[2],Inmates,",")
  305.         split("",InmateSet)
  306.         for (i in Inmates) {
  307.         Inmate = tolower(Inmates[i])
  308.         if (Debug && Inmate !~ /^[a-z][a-z0-9_]+$/)
  309.             print "Funny inmate name: " Inmate  > "/dev/stderr"
  310.         Balances[Inmate]
  311.         InmateSet[Inmate]
  312.         if (!(Inmate in ToMixed))
  313.             ToMixed[Inmate] = Inmates[i]
  314.         Inmates[i] = Inmate
  315.         }
  316.         if (Coord == "")
  317.         Coord = Inmates[1]
  318.     }
  319.     else
  320.         ExpandList(Fields[2])    # Catch any bad parties here
  321.     }
  322.     if (PrintRunning)
  323.     TailPrint($0)
  324. }
  325.  
  326. # DataLine: process a data line.
  327. # Globals: Header, NF, RealTotal, Coord, InmateSet,
  328. #          LastPay, Year, Debug, GlobalOwedBy, GlobalOwedTo
  329. # 93/03/06 Made ByLines and ToLines global to work around gawk bug
  330. function DataLine(Date,Carryover,PrintRunning,RunningBalance,LastPayment,
  331. PartyListGiven,ProcParties,StartDate,EndDate,MaxName,PTypesGiven,PTypes,
  332. FracExp,OwedTo,Cost,OwedBy,NumLines,LineNum,FieldOffset,
  333. BalanceLine,FieldsWanted,Format,Party,b,NumOwedByParties,PartyFrac,
  334. NumOwedToParties,CostChar,GoodParties) {
  335.     if (PrintRunning)
  336.     Format = "%-8s %7s %7s  %-" MaxName" s  %-" MaxName "s %s"
  337.     if (!Header) {
  338.     # Print header after initial alias lines
  339.     if (PrintRunning)
  340.         TailPrint(sprintf(Format,"Date","Amount","Fr.Amt","Owed-By",
  341.         "Owed-To","Expense-Description"))
  342.     Header = 1
  343.     }
  344.     # Default format is: date<ws>amount<ws>owed-by[<ws>owed-to]<tab>comment
  345.     # <ws> is tab or space.  Only the comment field can contain spaces within
  346.     # it.  The separator immediately before the comment field must be a tab so
  347.     # that spaces in the comment won't cause the first word of the comment
  348.     # field to be interpreted as the optional owed-to field.
  349.     # Replace with tabs the separators before the comment field that include
  350.     # spaces.
  351.     if (GlobalOwedTo != "") {
  352.     FieldsWanted = GlobalOwedBy == "" ? 4 : 3
  353.     while (NF < FieldsWanted && sub("[ \t]* [ \t]*","\t",$0))
  354.         ;
  355.     }
  356.     else if (DefaultSep) {
  357.     if ($0 !~ /\t/)
  358.     FileErrExit(\
  359.     "\nBad record: must include a tab before the comment field.",1)
  360.     while ($0 ~ / .*\t/)    # While there are still spaces before last tab
  361.         sub("[ \t]* [ \t]*","\t",$0)
  362.     }
  363.     Cost = $2
  364.     if (StartDate && Date < StartDate || EndDate && Date > EndDate)
  365.     return
  366.  
  367.     if (GlobalOwedBy != "") {
  368.     OwedBy = GlobalOwedBy
  369.     FieldOffset++
  370.     }
  371.     else
  372.     OwedBy = $3
  373.     if (GlobalOwedTo != "") {
  374.     OwedTo = GlobalOwedTo
  375.     FieldOffset++
  376.     }
  377.     else if (NF == (4-FieldOffset))
  378.     OwedTo = ToMixed[Coord]
  379.     else if (NF == (5-FieldOffset))
  380.     OwedTo = $(4-FieldOffset)
  381.     if (NF != (4-FieldOffset) && NF != (5-FieldOffset))
  382.     FileErrExit("Bad record: " NF " fields.",1)
  383.     
  384.     if (!Cost)
  385.     FileErrExit("\nBad record: 0 cost.",1)
  386.     # This deals with the p & n payment types.
  387.     if (PTypesGiven) {
  388.     # Cost will never be 0
  389.     CostChar = Cost > 0 ? "p" : "n"
  390.     if (CostChar in PTypes)
  391.         PTypesGiven = 0    # Got match; no need for further testing
  392.     }
  393.     if (Debug)
  394.     printf "Cost [pn] type is %s\n",CostChar > "/dev/stderr"
  395.  
  396.     NumOwedByParties = NetPartyList(OwedBy,1,PartyFrac)
  397.     FracExp = Cost / NumOwedByParties
  398.     NumOwedToParties = NetPartyList(OwedTo,-1,PartyFrac)
  399.     if (PTypesGiven) {
  400.     for (Party in ProcParties)
  401.         # party fraction will never be 0
  402.         if (Party in PartyFrac &&
  403.         ((PartyFrac[Party] < 0 ? "t" : "b") CostChar) in PTypes) {
  404.             PTypesGiven = 0
  405.             break
  406.         }
  407.     if (Debug)
  408.         printf "%s[bt] types match.\n",PTypesGiven ? "No " : "" \
  409.         > "/dev/stderr"
  410.     if (PTypesGiven)    # No match
  411.         return
  412.     }
  413.  
  414.     # For each party in PartyFrac[], multiply Cost by PartyFrac[party]
  415.     # and add it to Balances[] for that party.
  416.     for (Party in PartyFrac)
  417.     if (!PartyListGiven || Party in ProcParties) {
  418.         GoodParties = 1
  419.         # This line is the only one that does $ transfers in this program!
  420.         Balances[Party] += Cost * PartyFrac[Party]
  421.         if (Verbose)
  422.         VBalances[Party,sign(PartyFrac[Party]) "," sign(Cost)] += \
  423.         abs(Cost * PartyFrac[Party])
  424.         if (Debug)
  425.         printf "PartyFrac[%s]: %s\n",ToMixed[Party],PartyFrac[Party] \
  426.         > "/dev/stderr"
  427.     }
  428.     # Don't report expenses not applied to any we're counting
  429.     if (!GoodParties)
  430.     return
  431.  
  432.     RealTotal += abs(Cost)
  433.     if (PrintRunning) {
  434.     # Clear ByLines and ToLines since they are global for gawk
  435.     split("",ByLines," ")
  436.     split("",ToLines," ")
  437.     # Print this line over however many lines is neccessary for the
  438.     # owed-by and owed-to data to fit in the given field width.
  439.     NumLines = max(SplitLine(OwedBy,ByLines,MaxName),
  440.     SplitLine(OwedTo,ToLines,MaxName))
  441.     TailPrint(sprintf(Format,day2date(Date),sprintf("%7.2f",Cost),
  442.     sprintf("%7.2f",FracExp),ByLines[1],ToLines[1],$NF))
  443.     for (LineNum = 2; LineNum <= NumLines; LineNum++)
  444.         TailPrint(sprintf(Format,"","","",ByLines[LineNum],
  445.         ToLines[LineNum],""))
  446.     if (RunningBalance) {
  447.         for (Inmate in Balances)
  448.         if (Inmate != Coord &&
  449.         (Balance = dFmt(Balances[Inmate])) + 0 != 0 ||
  450.         Inmate in InmateSet)
  451.             BalanceLine = BalanceLine " " ToMixed[Inmate] "=" Balance
  452.         if (BalanceLine != "")
  453.         TailPrint(substr(BalanceLine,2))
  454.     }
  455.     }
  456.     OwedBy = tolower(OwedBy)
  457.     OwedTo = tolower(OwedTo)
  458.     if (Debug)
  459.     printf "Owed to: %s  Coord: %s  OwedBy: %s  Cost: %d\n",OwedTo,Coord,
  460.     OwedBy,Cost > "/dev/stderr"
  461.     # Track date & amount of last payments.  This is a payment if it is a
  462.     # negative expense applied by a single party to the coordinator.
  463.     if (LastPayment && OwedTo == Coord && OwedBy in Balances && Cost < 0) {
  464.     if (Debug)
  465.         print "This is a payment line." > "/dev/stderr"
  466.     if (!(OwedBy in LastPay) || Date > LastPay[OwedBy]) {
  467.         LastPay[OwedBy] = Date
  468.         LastAmt[OwedBy] = -Cost
  469.         if (Debug)
  470.         printf "Last payment by %s: %s\n",OwedBy,day2date(Date) \
  471.         > "/dev/stderr"
  472.     }
  473.     }
  474. }
  475.  
  476. # Globals: Year
  477. function GetDays(Date,  NElem,EYear,EMonth,EDay,DateElem) {
  478.     NElem = split(Date,DateElem,"/")
  479.     if (NElem == 3) {
  480.     EYear = DateElem[1]
  481.     if (EYear < 100)
  482.         EYear += 1900
  483.     EMonth = DateElem[2]
  484.     EDay = DateElem[3]
  485.     }
  486.     else if (NElem == 2) {
  487.     if (!Year)
  488.         ErrExit(\
  489.     "Year not set in datafile.  Must be set to use -l or -d options.\n",1)
  490.     EYear = Year
  491.     EMonth = DateElem[1]
  492.     EDay = DateElem[2]
  493.     }
  494.     else
  495.     FileErrExit("Bad date.",1)
  496.     return YMD2day(EYear,EMonth,EDay)
  497. }
  498.  
  499. # Uses globals:
  500. # VBalances[Party,dir-sign "," cost-sign] 
  501. #   dir-sign is 1 for expenses in which Party represents more
  502. #   of the owed-by field than the owed-to field, and -1 for expenses in which
  503. #   Party represents more of the owed-to field than than the owed by field. 
  504. #   cost-sign is 1 for expenses in which  the cost was positive, and -1 for
  505. #   expenses in which the cost was negative.
  506. function VerboseReport(MaxName,PartyListGiven,ProcParties,
  507. Format,DaySpan,YearSpan,Elem,Party,Num) {
  508.     DaySpan = LastDate - FirstDate + 1
  509.     YearSpan = DaySpan / 365.25
  510.     TailPrint(sprintf("First date: %s   Last date: %s",
  511.     day2date(FirstDate),day2date(LastDate)))
  512.     TailPrint(sprintf("Period: %d days  %.1f weeks  %.2f months  %.3f years",
  513.     DaySpan,DaySpan/7,YearSpan*12,YearSpan))
  514.     TailPrint("")
  515.     Format = "%-18s %8s %8s %8s %8s %8s"
  516.     TailPrint(sprintf(Format,"Account","Balance","Days","Weeks","Months",
  517.     "Years"))
  518.  
  519.     Num = split(\
  520. "owed-by+1,-1+1,1|owed-to+-1,-1+-1,1|negative+-1,-1+1,-1|positive+-1,1+1,1|"\
  521. "owed-by negative+1,-1|owed-by positive+1,1|owed-to negative+-1,-1|"\
  522. "owed-to positive+-1,1|debits+1,1+-1,-1|credits+1,-1+-1,1|"\
  523. "transfers+-1,-1+-1,1+1,-1+1,1",Elem,"|")
  524.     for (Party in Balances) {
  525.     if (!PartyListGiven || Party in ProcParties) {
  526.         VPartyReport(Party,Elem,Num,Format)
  527.         if (!PartyListGiven || NumPartiesGiven > 1) {
  528.         VBalances["All","-1,-1"] += VBalances[Party,"-1,-1"]
  529.         VBalances["All","-1,1"] += VBalances[Party,"-1,1"]
  530.         VBalances["All","1,-1"] += VBalances[Party,"1,-1"]
  531.         VBalances["All","1,1"] += VBalances[Party,"1,1"]
  532.         TailPrint("")
  533.         }
  534.     }
  535.     }
  536.     if (!PartyListGiven || NumPartiesGiven > 1) {
  537.     ToMixed["All"] = "All"
  538.     PartySpans["All"] = DaySpan
  539.     VPartyReport("All",Elem,Num,Format)
  540.     }
  541. }
  542.  
  543. function VPartyReport(Party,Elem,Num,Format,
  544. i,E2,Indices,Amt,j,Daily,DaySpan) {
  545.     DaySpan = PartySpans[Party]
  546.     TailPrint(sprintf(Format,ToMixed[Party],dFmt(Balances[Party]),
  547.     sprintf("%d",DaySpan),
  548.     sprintf("%.1f",DaySpan/7),sprintf("%.2f",DaySpan/365.25*12),
  549.     sprintf("%.3f",DaySpan/365.25)))
  550.     TailPrint(sprintf(Format,"  Expense type","$total","$/day","$/week",
  551.     "$/month","$/year"))
  552.     for (i = 1; i < Num; i++) {
  553.     Indices = split(Elem[i],E2,"+")
  554.     Amt = 0
  555.     for (j = 2; j <= Indices; j++) {
  556.         if (Debug)
  557.         printf "Adding VBalances[%s,%s] (%f) to %s\n",
  558.         Party,E2[j],VBalances[Party,E2[j]],E2[1] > "/dev/stderr"
  559.         Amt += VBalances[Party,E2[j]]
  560.     }
  561.     Daily = Amt/DaySpan
  562.     TailPrint(sprintf(Format,"  " E2[1],dFmt(Amt),dFmt(Daily),
  563.     dFmt(Daily*7),dFmt(Daily*365.25/12),dFmt(Daily*365.25)))
  564.     }
  565. }
  566.  
  567. # Globals: Balances, Coord, RealTotal, InmateSet, ToMixed
  568. function Report(Brief,MaxName,  Inmate,Date,Sum,Balance) {
  569.     Date = strftime("%a %h %d 19%y")
  570.     if (!Brief)
  571.     TailPrint(sprintf("\n***** Balances due to %s as of %s *****",
  572.     ToMixed[Coord],Date))
  573.     for (Inmate in Balances) {
  574.     if (Inmate != Coord) {
  575.         Sum += Balance = dFmt(Balances[Inmate])
  576.         # Report only on those who are currently in the 'All' group,
  577.         # or whose balance is at least a full 2c.  Exclude coordinator.
  578.         # Use sprintf'd balance because it is rounded to nearest cent.
  579.         if (abs(Balance) >= MinReptBal || Inmate in InmateSet) {
  580.         if (Brief)
  581.             TailPrint(sprintf("%-" MaxName "s  %8s",ToMixed[Inmate],
  582.             Balance))
  583.         else
  584.            TailPrint(sprintf("%-" MaxName "s  %8s",ToMixed[Inmate],
  585.            "$" Balance))
  586.         }
  587.     }
  588.     else if (Debug) {
  589.         printf "Excluded: %s. Balance = %f, Inmates[] = %d.\n",
  590.         ToMixed[Inmate],Balances[Inmate],Inmate in InmateSet > "/dev/stderr"
  591.     }
  592.     }
  593.     if (!Brief) {
  594.     TailPrint(sprintf("Total transfers: %9s","$" dFmt(RealTotal)))
  595.     TailPrint(sprintf("Sum of Balances: %9s","$" dFmt(Sum)))
  596.     }
  597. }
  598.  
  599. # Globals: Balances, Coord, ToMixed, LastDate, MinReptBal
  600. function ShowCarry(  Inmate,Date) {
  601.     Date = day2date(LastDate)
  602.     for (Inmate in Balances)
  603.     if (Inmate != Coord && abs(Balances[Inmate]) >= MinReptBal )
  604.         TailPrint(sprintf("%s\t%.2f\t%s\tCarryover of expenses",
  605.         Date,Balances[Inmate],ToMixed[Inmate]))
  606. }
  607.  
  608. # PartyList is a comma-separated list of parties, optionally including a
  609. # - sign followed by a list of parties to be removed from the first list.
  610. # Any of the parties may be an alias.
  611. # Output variables:
  612. # For each party in the list, the total number of times the party occurs in the
  613. # list is divided by the total number of parties (including multiples) in the
  614. # list and the result is multiplied by Mult and added to PartyFrac[party].
  615. # Parties that occur the same number of times on both sides of the - sign are
  616. # not added to PartyFrac[] (so no index will be created for them).
  617. # It is a terminal error for a party count to be reduce *below* 0,
  618. # or for there to be no parties left after subtraction.
  619. # The net number of parties in PartyList (after cancellation) is returned.
  620. function NetPartyList(PartyList,Mult,PartyFrac,
  621. PartyCount,Minus,Parties,NotParties,NumParties,Party,NotPartyCount) {
  622.     gsub(" ","",PartyList)
  623.     Minus = index(PartyList,"-")
  624.     if (Minus) {
  625.     Parties = substr(PartyList,1,Minus - 1)
  626.     NotParties = substr(PartyList,Minus + 1)
  627.     }
  628.     else
  629.     Parties = PartyList
  630.     NumParties = ExpandList(Parties,PartyCount)
  631.     if (NotParties != "")
  632.     NumParties -= ExpandList(NotParties,NotPartyCount)
  633.     if (!NumParties)
  634.     FileErrExit("No parties.",1)
  635.     for (Party in NotPartyCount) {
  636.     if (!(Party in PartyCount))
  637.         FileErrExit("\nNegative number of party \"" ToMixed[Party] "\".",1)
  638.     if (!(PartyCount[Party] -= NotPartyCount[Party]))
  639.         delete PartyCount[Party]
  640.     }
  641.     Mult /= NumParties
  642.     for (Party in PartyCount)
  643.     PartyFrac[Party] += PartyCount[Party] * Mult
  644.     return NumParties
  645. }
  646.  
  647. # Returns a count in PartyCount[] of the number of times each party occurs in
  648. # List, indexed by party name.
  649. # List is a comma-separated list of parties.  It should contain no whitespace.
  650. # Parties are recursively expanded using the global alias table Aliases[]
  651. # before being counted.
  652. # Alias is not normally passed in the initial call to ExpandList(); it is used
  653. # by ExpandList() to detect recursive aliases when it calls itself recursively
  654. # (it is set to the name that was expanded to the List being passed).
  655. # Globals: Aliases[]
  656. # Return value: The total number of parties found after alias expansion
  657. # (including multiple instances of a particular party).
  658. function ExpandList(List,PartyCount,Alias,
  659. Num,Party,Parties,PartyInd,LParty) {
  660.     Num = 0
  661. #    delete PartyCount[0]
  662.     split(List,Parties,",")
  663.     # Parties[1..m] now contains the party names to be counted.
  664.     for (PartyInd in Parties) {
  665.     Party = Parties[PartyInd]
  666.     if (Party == "")
  667.         FileErrExit(\
  668.     "\nEmpty party in list (superfluous comma, or whitespace in list?",1)
  669.     LParty = tolower(Party)
  670.     if (LParty == Alias)
  671.         FileErrExit("Recursive alias: " Party "=" List,1)
  672.     if (LParty in Aliases)
  673.         Num += ExpandList(Aliases[LParty],PartyCount,LParty)
  674.     else if (LParty in Balances) {
  675.         if (Debug)
  676.         printf "Incrementing PartyCount[%s]\n",Party > "/dev/stderr"
  677.         PartyCount[LParty]++
  678.         Num++
  679.     }
  680.     else
  681.         FileErrExit(Party ": bad party.",1)
  682.     }
  683.     return Num
  684. }
  685.  
  686. # Start of library routines
  687.  
  688. # Splits Line into lines in Lines with maximum length of MaxLen.
  689. # Lines are split on a non-alphanum character if possible.
  690. # The number of lines put in MaxLen is return.
  691. function SplitLine(Line,Lines,MaxLen,  Len,WordChars,LineNum,MaxLine) {
  692.     WordChars = "[a-zA-Z0-9_]"
  693.     LineNum = 0
  694.     while (Len = length(Line)) {
  695.     Lines[++LineNum] = substr(Line,1,MaxLen)
  696.     if (substr(Line,MaxLen,2) ~ "^" WordChars WordChars) {
  697.         MaxLine = Lines[LineNum]
  698.         sub(WordChars "*$","",MaxLine)
  699.         if (MaxLine != "")
  700.         Lines[LineNum] = MaxLine
  701.     }
  702.     Line = substr(Line,length(Lines[LineNum]) + 1)
  703.     }
  704.     return LineNum
  705. }
  706.  
  707. function max(a,b) {
  708.     if (a > b)
  709.     return a
  710.     else
  711.     return b
  712. }
  713.  
  714. # @(#) headtail.awk 95/06/20
  715. # 95/04/28 Added tail routines.
  716.  
  717. # Turn on screen-bounded printing.
  718. # Sets global vars LINES and COLUMNS.
  719. # Set either of them to 0 after calling this function if you do not want
  720. # limiting of lines or line length respectively.
  721. function HeadTailInit(  Cmd) {
  722.     # tput will use values in environment, but we want to avoid running
  723.     # it if possible.
  724.     if ("COLUMNS" in ENVIRON)
  725.     COLUMNS = ENVIRON["COLUMNS"]
  726.     else {
  727.     Cmd = "tput cols"
  728.     Cmd | getline COLUMNS
  729.     close(Cmd)
  730.     if (COLUMNS == "")
  731.         COLUMNS = 80
  732.     }
  733.     if ("LINES" in ENVIRON)
  734.     LINES = ENVIRON["LINES"]
  735.     else {
  736.     Cmd = "tput lines"
  737.     Cmd | getline LINES
  738.     close(Cmd)
  739.     if (LINES == "")
  740.         LINES = 24
  741.     }
  742. }
  743.  
  744. # Do screen-bound printing.  
  745. # If LINES  is >0, the last LINES-1 lines are kept in a circular buffer.  
  746. # When TailFlush() is called, they are printed.
  747. # If LINES = 0, all lines are printed immediately.
  748. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  749. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  750. # saves lines in TailLines[] from 1..LINES-1
  751. # Embedded newlines split the line into multiple lines; trailing newlines are
  752. # stripped.  Tabs are expanded to spaces.
  753. function TailPrint(Line) {
  754.     if (!LINES)
  755.     print Line
  756.     else {
  757.     if (++TailPtr > (LINES-1))
  758.         TailPtr = 1
  759.     TailLines[TailPtr] = Line
  760.     }
  761. }
  762.  
  763. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  764.     if (!LINES)
  765.     return
  766.     NumPrinted = 0
  767.     PrintLines = LINES-1
  768.     # Since lines may contain multiple lines, we must create a buffer to be
  769.     # printed by reading line buffer backwards.
  770.     # Stop when we've copied enough lines, or if we wrap around to the end and
  771.     # find that the entire line buffer wasn't used.
  772.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  773.     # Split line into individual lines, then process them last to first
  774.     Num = split(TailLines[TailPtr],Lines,"\n")
  775.     for (i = Num; i >= 1; i--) {
  776.         Line = Lines[i]
  777.         if (i == Num && Line == "")    # discard trailing newline
  778.         continue
  779.         # Put this line at the front of the print buffer
  780.         if (COLUMNS)
  781.         Buffer = substr(TabEx(Line),1,COLUMNS - 1) "\n" Buffer
  782.         else
  783.         Buffer = Line "\n" Buffer
  784.         if (++NumPrinted == PrintLines)
  785.         break
  786.     }
  787.     if (!--TailPtr)    # Wrap pointer if neccessary
  788.         TailPtr = PrintLines
  789.     }
  790.     printf "%s",Buffer
  791. }
  792.  
  793. # Do screen-bound printing.  
  794. # If LINES >0, returns 0 when LINES-1 lines have been printed by HeadPrint().
  795. # Otherwise returns 1.
  796. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  797. # Global vars: uses LINES & COLUMNS; sets/uses LinesPrinted.
  798. # Line should not include newlines.
  799. function HeadPrint(Line) {
  800.     # Check first, in case some calls of this function to not check return
  801.     # value, and in case LINES is 1.
  802.     if (LINES && LinesPrinted >= (LINES-1))
  803.     return 0
  804.     if (COLUMNS)
  805.     print substr(Line,1,COLUMNS - 1)
  806.     else
  807.     print Line
  808.     if (LINES && ++LinesPrinted >= (LINES-1))
  809.     return 0
  810.     return 1
  811. }
  812.  
  813. # Expand tabs in Line
  814. function TabEx(Line,  Segs,i,Num,S) {
  815.     Num = split(Line,Segs,"\t")
  816.     Line = ""
  817.     for (i = 1; i < Num; i++) {
  818.     S = Segs[i]
  819.     Line = Line S substr("        ",length(S) % 8 + 1)
  820.     }
  821.     return Line Segs[Num]
  822. }
  823.  
  824. function LineErr(S) {
  825.     ErrPrint("Error on line " FNR ": " S)
  826. }
  827.  
  828. function FileErr(S) {
  829.     ErrPrint("Error on line " FNR " of file \"" FILENAME "\": " S)
  830. }
  831.  
  832. function FileErrExit(S,ExitVal) {
  833.     FileErr(S)
  834.     Err = ExitVal
  835.     exit(ExitVal)
  836. }
  837.  
  838. function ErrPrint(S) {
  839.     print S > "/dev/stderr"
  840. }
  841.  
  842. #function ErrPrint(S) {
  843. #    print S | "cat 1>&2"
  844. #}
  845.  
  846. function ErrExit(S,ExitVal) {
  847.     ErrPrint(S)
  848.     Err = ExitVal
  849.     exit(ExitVal)
  850. }
  851.  
  852. # MakeSet: make a set from a list.
  853. # An index with the name of each element of the list
  854. # is created in the given array.
  855. # Input variables: 
  856. # Elements is a string containing the list of elements.
  857. # Sep is the character that separates the elements of the list.
  858. # Output variables:
  859. # Set is the array.
  860. # Return value: the number of elements added to the set.
  861. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  862.     Num = split(Elements,Names,Sep)
  863.     for (i = 1; i <= Num; i++)
  864.     Set[Names[i]]
  865.     return Num
  866. }
  867.  
  868. ### Begin sign routines
  869.  
  870. function sign(value) {
  871.     if (value > 0)
  872.     return 1
  873.     else if (value < 0)
  874.     return -1
  875.     else
  876.     return 0
  877. }
  878.  
  879. function abs(value) {
  880.     if (value+0 >= 0)
  881.     return value
  882.     else
  883.     return -value
  884. }
  885.  
  886. ### End sign routines
  887. ### Begin date-days routines
  888.  
  889. # YMD2day(year,month,day-of-month) returns the number of days that passed from 
  890. # 1970 Jan 1 to the given date.
  891. # All parameters should be given in numeric form.
  892. # If year < 70, it is assumed to be part of the 2000 century
  893. # If year in (70..99), 1900.
  894. # Globals: sets and uses MDays[]
  895. function YMD2day(Year,Month,Day,   LeapDays) {
  896.     Year+=0
  897.     Month+=0
  898.     if (Year < 70)
  899.     Year += 100
  900.     else if (Year >= 100)
  901.     Year -= 1900
  902.     # Year is now the number of years since 1900.
  903.     LeapDays = int((Year - 68) / 4)
  904.     if (Month <= 2 && Year % 4 == 0)
  905.     LeapDays -= 1
  906.     if (!(0 in MDays))
  907.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  908.     return (Year - 70) * 365 + MDays[Month] + Day - 1 + LeapDays
  909. }
  910.  
  911. # date2day("yy/mm/dd") returns the number of days that passed from 
  912. # 1970 Jan 1 to the given date.  -1 is returned on error.
  913. # The fields are returned in Fields: year in Fields[1], month in Fields[2],
  914. # and day (if given) in Fields[3].
  915. function date2day(Date,Fields,  Num,Year,Month) {
  916.     Num = split(Date,Fields,"/")
  917.     if (Num != 2 && Num != 3)
  918.     return -1
  919.     if (!(Year = Fields[1] + 0) || !(Month = Fields[2] + 0))
  920.     return -1
  921.     if (Num == 3)
  922.     Day = Fields[3]
  923.     return YMD2day(Year,Month,Day)
  924. }
  925.  
  926. # diffdays(year1,month1,day-of-month1,year2,month2,day-of-month2)
  927. # returns the number of complete days that passed from date 1 to date 2
  928. function diffdays(year1,month1,day1,year2,month2,day2) {
  929.     return date2days(year2,month2,day2) - date2days(year1,month1,day1)
  930. }
  931.  
  932. # Given an epoch month, return the first day of that month
  933. function month2day(Month) {
  934.     return YMD2day(int(Month/12) + 1970,Month % 12 + 1,1)
  935. }
  936.  
  937. # Given an epoch day, returns epoch month
  938. function day2month(Day,  Date) {
  939.     day2YMD(Day,Date)
  940.     return (Date["y"]-1970)*12 + Date["m"]-1
  941. }
  942.  
  943. # Given an epoch month, returns the number of days in that month.
  944. function monthdays(month,  year) {
  945.     if (!(0 in MDur))
  946.     split("31 28 31 30 31 30 31 31 30 31 30 31",MDur)
  947.     year = int(month/12)
  948.     month = month%12+1
  949.     return (!((year+2)%4) && month == 2) ? 29 : MDur[month]
  950. }
  951.  
  952. # Given an epoch day (day since 1970 Jan 1; day 0 = 1970 Jan 1, etc.), 
  953. # returns the date elements in Date:
  954. # Date["y"] = year (4 digits), Date["m"] = month (jan = 1, etc.),
  955. # Date["d"] = day of month.
  956. # Globals: Sets/uses MDays[].
  957. function day2YMD(Day,Date,  QYears,Year,NonLeapYears,Month) {
  958.     if (!(0 in LDays)) {
  959.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  960.     split("0 31 60 91 121 152 182 213 244 274 305 335 366",LDays," ")
  961.     }
  962.     Day += 365
  963.     # Day is now # of days since Jan 1 1969.  1968 was a leap year.
  964.     QYears = int(Day / (365*4+1))
  965.     Year = 1969 + QYears * 4
  966.     Day -= QYears * (365*4+1)
  967.     # Day now contains no complete leap years.
  968.     Year += NonLeapYears = int(Day/365)
  969.     Leap = !(Year % 4)
  970.     Day -= NonLeapYears * 365
  971.     # Day now contains the day of year.
  972.     # Find the month.  Divide day by 32 to get either the correct month or
  973.     # the month prior to it.
  974.     Month = int(Day++ / 32) + 1
  975.     if (Day > (Leap ? LDays[Month+1] : MDays[Month+1]))
  976.     Month++
  977.     Day -= Leap ? LDays[Month] : MDays[Month]
  978.     Date["d"] = Day
  979.     Date["m"] = Month
  980.     Date["y"] = Year
  981. }
  982.  
  983. # Given a month number, return a date in the form yy/mm
  984. function month2date(MonthNum) {
  985.     return sprintf("%02d/%02d",(MonthNum / 12 + 70) % 100, MonthNum % 12 + 1)
  986. }
  987.  
  988. # Given a day number, return a date in the form yy/mm/dd
  989. function day2date(day) {
  990.     day2YMD(day,Date)
  991.     return sprintf("%02d/%02d/%02d",Date["y"]%100,Date["m"],Date["d"])
  992. }
  993.  
  994. ### End date-days routines
  995.  
  996. ### Start of ProcArgs library
  997. # @(#) ProcArgs 1.11 96/12/08
  998. # 92/02/29 john h. dubois iii (john@armory.com)
  999. # 93/07/18 Added "#" arg type
  1000. # 93/09/26 Do not count -h against MinArgs
  1001. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  1002. #          Removed meaning of "+" or "-" by itself.
  1003. # 94/03/08 Added & option and *()< option types.
  1004. # 94/04/02 Added NoRCopt to Opts()
  1005. # 94/06/11 Mark numeric variables as such.
  1006. # 94/07/08 Opts(): Do not require any args if h option is given.
  1007. # 95/01/22 Record options given more than once.  Record option num in argv.
  1008. # 95/06/08 Added ExclusiveOptions().
  1009. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  1010. #          Expand $VARNAME at the start of its filenames.
  1011. #          Let varname=0 and -option- turn off an option.
  1012. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  1013. #          of the vars should be searched for in the environment.
  1014. #          Check for duplicate rcfiles.
  1015. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  1016. #          now return various negatives values on error, not just -1, and
  1017. #          Opts() may set Err to various positive values, not just 1.
  1018. #          Added AllowUnrecOpt.
  1019. # 96/05/23 Check type given for & option
  1020. # 96/06/15 Re-port to awk
  1021. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  1022. #          used by other functions.
  1023. # 96/10/15 Added OptChars
  1024. # 96/11/01 Added exOpts arg to Opts()
  1025. # 96/11/16 Added ; type
  1026. # 96/12/08 Added Opt2Set() & Opt2Sets()
  1027. # 96/12/27 Added CmdLineOpt()
  1028.  
  1029. # optlist is a string which contains all of the possible command line options.
  1030. # A character followed by certain characters indicates that the option takes
  1031. # an argument, with type as follows:
  1032. # :    String argument
  1033. # ;    Non-empty string argument
  1034. # *    Floating point argument
  1035. # (    Non-negative floating point argument
  1036. # )    Positive floating point argument
  1037. # #    Integer argument
  1038. # <    Non-negative integer argument
  1039. # >    Positive integer argument
  1040. # The only difference the type of argument makes is in the runtime argument
  1041. # error checking that is done.
  1042.  
  1043. # The & option is a special case used to get numeric options without the
  1044. # user having to give an option character.  It is shorthand for [-+.0-9].
  1045. # If & is included in optlist and an option string that begins with one of
  1046. # these characters is seen, the value given to "&" will include the first
  1047. # char of the option.  & must be followed by a type character other than ":"
  1048. # or ";".
  1049. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1050.  
  1051. # Strings in argv[] which begin with "-" or "+" are taken to be
  1052. # strings of options, except that a string which consists solely of "-"
  1053. # or "+" is taken to be a non-option string; like other non-option strings,
  1054. # it stops the scanning of argv and is left in argv[].
  1055. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1056. # If an option takes an argument, the argument may either immediately
  1057. # follow it or be given separately.
  1058. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1059. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1060. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1061. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1062. # parameter to explicitly set the option-specifier characters.
  1063.  
  1064. # If an option that does not take an argument is given,
  1065. # an index with its name is created in Options and its value is set to the
  1066. # number of times it occurs in argv[].
  1067.  
  1068. # If an option that does take an argument is given, an index with its name is
  1069. # created in Options and its value is set to the value of the argument given
  1070. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1071. # If an option that takes an argument is given more than once,
  1072. # Options[option-name,"count"] is incremented, and the value is assigned to
  1073. # the index (option-name,instance) where instance is 2 for the second occurance
  1074. # of the option, etc.
  1075. # In other words, the first time an option with a value is encountered, the
  1076. # value is assigned to an index consisting only of its name; for any further
  1077. # occurances of the option, the value index has an extra (count) dimension.
  1078.  
  1079. # The sequence number for each option found in argv[] is stored in
  1080. # Options[option-name,"num",instance], where instance is 1 for the first
  1081. # occurance of the option, etc.  The sequence number starts at 1 and is
  1082. # incremented for each option, both those that have a value and those that
  1083. # do not.  Options set from a config file have a value of 0 assigned to this.
  1084.  
  1085. # Options and their arguments are deleted from argv.
  1086. # Note that this means that there may be gaps left in the indices of argv[].
  1087. # If compress is nonzero, argv[] is packed by moving its elements so that
  1088. # they have contiguous integer indices starting with 0.
  1089. # Option processing will stop with the first unrecognized option, just as
  1090. # though -- was given except that unlike -- the unrecognized option will not be
  1091. # removed from ARGV[].  Normally, an error value is returned in this case.
  1092. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1093. # be found, so the number of remaining arguments is returned instead.
  1094. # If OptChars is not a null string, it is the set of characters that indicate
  1095. # that an argument is an option string if the string begins with one of the
  1096. # characters.  A string consisting solely of two of the same option-indicator
  1097. # characters stops the scanning of argv[].  The default is "-+".
  1098. # argv[0] is not examined.
  1099. # The number of arguments left in argc is returned.
  1100. # If an error occurs, the global string OptErr is set to an error message
  1101. # and a negative value is returned.
  1102. # Current error values:
  1103. # -1: option that required an argument did not get it.
  1104. # -2: argument of incorrect type supplied for an option.
  1105. # -3: unrecognized (invalid) option.
  1106. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1107. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1108. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1109. {
  1110. # ArgNum is the index of the argument being processed.
  1111. # ArgsLeft is the number of arguments left in argv.
  1112. # Arg is the argument being processed.
  1113. # ArgLen is the length of the argument being processed.
  1114. # ArgInd is the position of the character in Arg being processed.
  1115. # Option is the character in Arg being processed.
  1116. # Pos is the position in OptList of the option being processed.
  1117. # NumOpt is true if a numeric option may be given.
  1118.     ArgsLeft = argc
  1119.     NumOpt = index(OptList,"&")
  1120.     OptionNum = 0
  1121.     if (OptChars == "")
  1122.     OptChars = "-+"
  1123.     while (OptChars != "") {
  1124.     c = substr(OptChars,1,1)
  1125.     OptChars = substr(OptChars,2)
  1126.     OptCharSet[c]
  1127.     OptTerm[c c]
  1128.     }
  1129.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1130.     Arg = argv[ArgNum]
  1131.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1132.         break    # Not an option; quit
  1133.     if (Arg in OptTerm) {
  1134.         delete argv[ArgNum]
  1135.         ArgsLeft--
  1136.         break
  1137.     }
  1138.     ArgLen = length(Arg)
  1139.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1140.         Option = substr(Arg,ArgInd,1)
  1141.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1142.         # If this option is a numeric option, make its flag be & and
  1143.         # its option string flag position be the position of & in
  1144.         # the option string.
  1145.         Option = "&"
  1146.         Pos = NumOpt
  1147.         # Prefix Arg with a char so that ArgInd will point to the
  1148.         # first char of the numeric option.
  1149.         Arg = "&" Arg
  1150.         ArgLen++
  1151.         }
  1152.         # Find position of flag in option string, to get its type (if any).
  1153.         # Disallow & as literal flag.
  1154.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1155.         if (AllowUnrecOpt) {
  1156.             Escape = 1
  1157.             break
  1158.         }
  1159.         else {
  1160.             OptErr = "Invalid option: " specGiven Option
  1161.             return -3
  1162.         }
  1163.         }
  1164.  
  1165.         # Find what the value of the option will be if it takes one.
  1166.         # NeedNextOpt is true if the option specifier is the last char of
  1167.         # this arg, which means that if the option requires a value it is
  1168.         # the next arg.
  1169.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1170.         if (GotValue = ArgNum + 1 < argc)
  1171.             Value = argv[ArgNum+1]
  1172.         }
  1173.         else {    # Value is included with option
  1174.         Value = substr(Arg,ArgInd + 1)
  1175.         GotValue = 1
  1176.         }
  1177.  
  1178.         if (HadValue = AssignVal(Option,Value,Options,
  1179.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1180.         specGiven)) {
  1181.         if (HadValue < 0)    # error occured
  1182.             return HadValue
  1183.         if (HadValue == 2)
  1184.             ArgInd++    # Account for the single-char value we used.
  1185.         else {
  1186.             if (NeedNextOpt) {    # option took next arg as value
  1187.             delete argv[++ArgNum]
  1188.             ArgsLeft--
  1189.             }
  1190.             break    # This option has been used up
  1191.         }
  1192.         }
  1193.     }
  1194.     if (Escape)
  1195.         break
  1196.     # Do not delete arg until after processing of it, so that if it is not
  1197.     # recognized it can be left in ARGV[].
  1198.     delete argv[ArgNum]
  1199.     ArgsLeft--
  1200.     }
  1201.     if (compress != 0) {
  1202.     dest = 1
  1203.     src = argc - ArgsLeft + 1
  1204.     for (count = ArgsLeft - 1; count; count--) {
  1205.         ARGV[dest] = ARGV[src]
  1206.         dest++
  1207.         src++
  1208.     }
  1209.     }
  1210.     return ArgsLeft
  1211. }
  1212.  
  1213. # Assignment to values in Options[] occurs only in this function.
  1214. # Option: Option specifier character.
  1215. # Value: Value to be assigned to option, if it takes a value.
  1216. # Options[]: Options array to return values in.
  1217. # ArgType: Argument type specifier character.
  1218. # GotValue: Whether any value is available to be assigned to this option.
  1219. # Name: Name of option being processed.
  1220. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1221. #     or 0 if it was given in a config file or in the environment.
  1222. # SingleOpt: true if the value (if any) that is available for this option was
  1223. #     given as part of the same command line arg as the option.  Used only for
  1224. #     options from the command line.
  1225. # specGiven is the option specifier character use, if any (e.g. - or +),
  1226. # for use in error messages.
  1227. # Global variables: OptErr
  1228. # Return value: negative value on error, 0 if option did not require an
  1229. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1230. # the arg.
  1231. # Current error values:
  1232. # -1: Option that required an argument did not get it.
  1233. # -2: Value of incorrect type supplied for option.
  1234. # -3: Bad type given for option &
  1235. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1236. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1237.     # If option takes a value...    [
  1238.     NumTypes = "*()#<>]"
  1239.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1240.     OptErr = "Bad type given for & option"
  1241.     return -3
  1242.     }
  1243.  
  1244.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1245.     if (!GotValue) {
  1246.         if (Name != "")
  1247.         OptErr = "Variable requires a value -- " Name
  1248.         else
  1249.         OptErr = "option requires an argument -- " Option
  1250.         return -1
  1251.     }
  1252.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1253.         OptErr = Err
  1254.         return -2
  1255.     }
  1256.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1257.     if (ArgType != ":" && ArgType != ";")
  1258.         Value += 0
  1259.     if ((Instance = ++Options[Option,"count"]) > 1)
  1260.         Options[Option,Instance] = Value
  1261.     else
  1262.         Options[Option] = Value
  1263.     }
  1264.     # If this is an environ or rcfile assignment & it was given a value...
  1265.     else if (!OptionNum && Value != "") {
  1266.     UsedValue = 1
  1267.     # If the value is "0" or "-" and this is the first instance of it,
  1268.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1269.     # turn off an option (for the simple "Option in Options" test) in such
  1270.     # a way that it cannot be turned on in a later file.
  1271.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1272.         Instance = 1
  1273.     else
  1274.         Instance = ++Options[Option]
  1275.     # Save the value even though this is a flag
  1276.     Options[Option,Instance] = Value
  1277.     }
  1278.     # If this is a command line flag and has a - following it in the same arg,
  1279.     # it is being turned off.
  1280.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1281.     UsedValue = 2
  1282.     if (Option in Options)
  1283.         Instance = ++Options[Option]
  1284.     else
  1285.         Instance = 1
  1286.     Options[Option,Instance]
  1287.     }
  1288.     # If this is a flag assignment without a value, increment the count for the
  1289.     # flag unless it was turned off.  The indicator for a flag being turned off
  1290.     # is that the flag index has not been set in Options[] but it has an
  1291.     # instance count.
  1292.     else if (Option in Options || !((Option,1) in Options))
  1293.     # Increment number of times this flag seen; will inc null value to 1
  1294.     Instance = ++Options[Option]
  1295.     Options[Option,"num",Instance] = OptionNum
  1296.     return UsedValue
  1297. }
  1298.  
  1299. # Option is the option letter
  1300. # Value is the value being assigned
  1301. # Name is the var name of the option, if any
  1302. # ArgType is one of:
  1303. # :    String argument
  1304. # ;    Non-null string argument
  1305. # *    Floating point argument
  1306. # (    Non-negative floating point argument
  1307. # )    Positive floating point argument
  1308. # #    Integer argument
  1309. # <    Non-negative integer argument
  1310. # >    Positive integer argument
  1311. # specGiven is the option specifier character use, if any (e.g. - or +),
  1312. # for use in error messages.
  1313. # Returns null on success, err string on error
  1314. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1315.     if (ArgType == ":")
  1316.     return ""
  1317.     if (ArgType == ";") {
  1318.     if (Value == "")
  1319.         Err = "must be a non-empty string"
  1320.     }
  1321.     # A number begins with optional + or -, and is followed by a string of
  1322.     # digits or a decimal with digits before it, after it, or both
  1323.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1324.     Err = "must be a number"
  1325.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1326.     Err = "may not include a fraction"
  1327.     else if (ArgType ~ "[()<>]" && Value < 0)
  1328.     Err = "may not be negative"
  1329.     # (
  1330.     else if (ArgType ~ "[)>]" && Value == 0)
  1331.     Err = "must be a positive number"
  1332.     if (Err != "") {
  1333.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1334.     if (Name != "")
  1335.         return ErrStr "variable " substr(Name,1,1) " " Err
  1336.     else {
  1337.         if (Option == "&")
  1338.         Option = Value
  1339.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1340.     }
  1341.     }
  1342.     else
  1343.     return ""
  1344. }
  1345.  
  1346. # Note: only the above functions are needed by ProcArgs.
  1347. # The rest of these functions call ProcArgs() and also do other
  1348. # option-processing stuff.
  1349.  
  1350. # Opts: Process command line arguments.
  1351. # Opts processes command line arguments using ProcArgs()
  1352. # and checks for errors.  If an error occurs, a message is printed
  1353. # and the program is exited.
  1354. #
  1355. # Input variables:
  1356. # Name is the name of the program, for error messages.
  1357. # Usage is a usage message, for error messages.
  1358. # OptList the option description string, as used by ProcArgs().
  1359. # MinArgs is the minimum number of non-option arguments that this
  1360. # program should have, non including ARGV[0] and +h.
  1361. # If the program does not require any non-option arguments,
  1362. # MinArgs should be omitted or given as 0.
  1363. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1364. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1365. # by the value of the environment variable HOME.  If a filename begins with
  1366. # $, the part from the character after the $ up until (but not including)
  1367. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1368. # environment; if found its value will be substituted, if not the filename will
  1369. # be discarded.
  1370. # rcfiles are read in the order given.
  1371. # Values given in them will not override values given on the command line,
  1372. # and values given in later files will not override those set in earlier
  1373. # files, because AssignVal() will store each with a different instance index.
  1374. # The first instance of each variable, either on the command line or in an
  1375. # rcfile, will be stored with no instance index, and this is the value
  1376. # normally used by programs that call this function.
  1377. # VarNames is a comma-separated list of variable names to map to options,
  1378. # in the same order as the options are given in OptList.
  1379. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1380. # searched for in the environment.  If set to -1, all values will be searched
  1381. # for in the environment.  Values given in the environment will override
  1382. # those given in the rcfiles but not those given on the command line.
  1383. # NoRCopt, if given, is an additional letter option that if given on the
  1384. # command line prevents the rcfiles from being read.
  1385. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1386. # ExclusiveOptions() for a description of exOpts.
  1387. # Special options:
  1388. # If x is made an option and is given, some debugging info is output.
  1389. # h is assumed to be the help option.
  1390.  
  1391. # Global variables:
  1392. # The command line arguments are taken from ARGV[].
  1393. # The arguments that are option specifiers and values are removed from
  1394. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1395. # The number of elements in ARGV[] should be in ARGC.
  1396. # After processing, ARGC is set to the number of elements left in ARGV[].
  1397. # The option values are put in Options[].
  1398. # On error, Err is set to a positive integer value so it can be checked for in
  1399. # an END block.
  1400. # Return value: The number of elements left in ARGV is returned.
  1401. # Must keep OptErr global since it may be set by InitOpts().
  1402. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1403. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1404.     if (MinArgs == "")
  1405.     MinArgs = 0
  1406.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1407.     optChars)
  1408.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1409.     if (ArgsLeft >= 0) {
  1410.         OptErr = "Not enough arguments"
  1411.         Err = 4
  1412.     }
  1413.     else
  1414.         Err = -ArgsLeft
  1415.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1416.     Name,OptErr,Usage > "/dev/stderr"
  1417.     exit 1
  1418.     }
  1419.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1420.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1421.     {
  1422.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1423.     Err = -e
  1424.     exit 1
  1425.     }
  1426.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1427.     {
  1428.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1429.     Err = 1
  1430.     exit 1
  1431.     }
  1432.     return ArgsLeft
  1433. }
  1434.  
  1435. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1436. # <variable-name><assignment-char><value>.
  1437. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1438. # line and whitespace between the variable name and the assignment character) 
  1439. # is stripped.  Lines that do not contain an assignment operator or which
  1440. # contain a null variable name are ignored, other than possibly being noted in
  1441. # the return value.  If more than one assignment is made to a variable, the
  1442. # first assignment is used.
  1443. # Input variables:
  1444. # File is the file to read.
  1445. # Comment is the line-comment character.  If it is found as the first non-
  1446. #     whitespace character on a line, the line is ignored.
  1447. # Assign is the assignment string.  The first instance of Assign on a line
  1448. #     separates the variable name from its value.
  1449. # If StripWhite is true, whitespace around the value (whitespace between the
  1450. #     assignment char and trailing whitespace on the line) is stripped.
  1451. # VarPat is a pattern that variable names must match.  
  1452. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1453. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1454. #     a line; no assignment operator is needed.  These variables are set in
  1455. #     the output array with a null value.  Lines containing nothing but
  1456. #     whitespace are still ignored.
  1457. # Output variables:
  1458. # Values[] contains the assignments, with the indexes being the variable names
  1459. #     and the values being the assigned values.
  1460. # Lines[] contains the line number that each variable occured on.  A flag set
  1461. #     is record by giving it an index in Lines[] but not in Values[].
  1462. # Return value:
  1463. # If any errors occur, a string consisting of descriptions of the errors
  1464. # separated by newlines is returned.  In no case will the string start with a
  1465. # numeric value.  If no errors occur,  the number of lines read is returned.
  1466. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1467. FlagsOK,
  1468. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1469.     if (Comment != "")
  1470.     Comment = "^" Comment
  1471.     AssignLen = length(Assign)
  1472.     if (VarPat == "")
  1473.     VarPat = "."    # null varname not allowed
  1474.     while ((Status = (getline Line < File)) == 1) {
  1475.     LineNum++
  1476.     sub("^[ \t]+","",Line)
  1477.     if (Line == "")        # blank line
  1478.         continue
  1479.     if (Comment != "" && Line ~ Comment)
  1480.         continue
  1481.     if (Pos = index(Line,Assign)) {
  1482.         Var = substr(Line,1,Pos-1)
  1483.         Val = substr(Line,Pos+AssignLen)
  1484.         if (StripWhite) {
  1485.         sub("^[ \t]+","",Val)
  1486.         sub("[ \t]+$","",Val)
  1487.         }
  1488.     }
  1489.     else {
  1490.         Var = Line    # If no value, var is entire line
  1491.         Val = ""
  1492.     }
  1493.     if (!FlagsOK && Val == "") {
  1494.         Errs = Errs \
  1495.         sprintf("\nBad assignment on line %d of file %s: %s",
  1496.         LineNum,File,Line)
  1497.         continue
  1498.     }
  1499.     sub("[ \t]+$","",Var)
  1500.     if (Var !~ VarPat) {
  1501.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1502.         LineNum,File,Var)
  1503.         continue
  1504.     }
  1505.     if (!(Var in Lines)) {
  1506.         Lines[Var] = LineNum
  1507.         if (Pos)
  1508.         Values[Var] = Val
  1509.     }
  1510.     }
  1511.     if (Status)
  1512.     Errs = Errs "\nCould not read file " File
  1513.     close(File)
  1514.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1515. }
  1516.  
  1517. # Variables:
  1518. # Data is stored in Options[].
  1519. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1520. # Global vars:
  1521. # Sets OptErr.  Uses ENVIRON[].
  1522. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1523. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1524. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1525. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1526.     split("",filesRead,"")    # make awk know this is an array
  1527.     NumVars = split(VarNames,Vars,",")
  1528.     TypesInd = Ret = 0
  1529.     if (EnvSearch == -1)
  1530.     EnvSearch = NumVars
  1531.     for (i = 1; i <= NumVars; i++) {
  1532.     Var = Vars[i]
  1533.     CharOpt = substr(OptList,++TypesInd,1)
  1534.     if (CharOpt ~ "^[:;*()#<>&]$")
  1535.         CharOpt = substr(OptList,++TypesInd,1)
  1536.     Map[Var] = CharOpt
  1537.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1538.     # Do not overwrite entries from environment
  1539.     if (i <= EnvSearch && Var in ENVIRON &&
  1540.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1541.         return Err
  1542.     }
  1543.  
  1544.     numrcFiles = split(rcFiles,fNames,":")
  1545.     for (i = 1; i <= numrcFiles; i++) {
  1546.     rcFile = fNames[i]
  1547.     if (rcFile ~ "^~/")
  1548.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1549.     else if (rcFile ~ /^\$/) {
  1550.         rcFile = substr(rcFile,2)
  1551.         match(rcFile,"^[a-zA-Z0-9_]*")
  1552.         envvar = substr(rcFile,1,RLENGTH)
  1553.         if (envvar in ENVIRON)
  1554.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1555.         else
  1556.         continue
  1557.     }
  1558.     if (rcFile in filesRead)
  1559.         continue
  1560.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1561.     # may be the same
  1562.     filesRead[rcFile]
  1563.     if ("x" in Options)
  1564.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1565.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1566.     if (retStr > 0)
  1567.         READ_RCFILE = 1
  1568.     else if (ret != "") {
  1569.         OptErr = retStr
  1570.         Ret = -1
  1571.     }
  1572.     for (Var in Lines)
  1573.         if (Var in Map) {
  1574.         if ((Err = AssignVal(Map[Var],
  1575.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1576.         Var in Values,Var,0)) < 0)
  1577.             return Err
  1578.         }
  1579.         else {
  1580.         OptErr = sprintf(\
  1581.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1582.         Lines[Var],rcFile)
  1583.         Ret = -1
  1584.         }
  1585.     }
  1586.  
  1587.     if ("x" in Options)
  1588.     for (Var in Map)
  1589.         if (Map[Var] in Options)
  1590.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1591.         "/dev/stderr"
  1592.         else
  1593.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1594.     return Ret
  1595. }
  1596.  
  1597. # OptSets is a semicolon-separated list of sets of option sets.
  1598. # Within a list of option sets, the option sets are separated by commas.  For
  1599. # each set of sets, if any option in one of the sets is in Options[] AND any
  1600. # option in one of the other sets is in Options[], an error string is returned.
  1601. # If no conflicts are found, nothing is returned.
  1602. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1603. # the exclusions presented by the first set of sets (ab,def,g) if:
  1604. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1605. # (a or b is in Options[]) AND (g is in Options) OR
  1606. # (d, e, or f is in Options[]) AND (g is in Options)
  1607. # An error will be returned due to the exclusions presented by the second set
  1608. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1609. # todo: make options given on command line unset options given in config file
  1610. # todo: that they conflict with.
  1611. function ExclusiveOptions(OptSets,Options,
  1612. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1613. SetNum,OSetNum) {
  1614.     NumSetSets = split(OptSets,SetSets,";")
  1615.     # For each set of sets...
  1616.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1617.     # NumSets is the number of sets in this set of sets.
  1618.     NumSets = split(SetSets[SetSet],Sets,",")
  1619.     # For each set in a set of sets except the last...
  1620.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1621.         s1 = Sets[SetNum]
  1622.         L1 = length(s1)
  1623.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1624.         # If any of the options in this set was given, check whether
  1625.         # any of the options in the other sets was given.  Only check
  1626.         # later sets since earlier sets will have already been checked
  1627.         # against this set.
  1628.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1629.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1630.             s2 = Sets[OSetNum]
  1631.             L2 = length(s2)
  1632.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1633.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1634.                 ErrStr = ErrStr "\n"\
  1635.                 sprintf("Cannot give both %s and %s options.",
  1636.                 c1,c2)
  1637.             }
  1638.     }
  1639.     }
  1640.     if (ErrStr != "")
  1641.     return substr(ErrStr,2)
  1642.     return ""
  1643. }
  1644.  
  1645. # The value of each instance of option Opt that occurs in Options[] is made an
  1646. # index of Set[].
  1647. # The return value is the number of instances of Opt in Options.
  1648. function Opt2Set(Options,Opt,Set,  count) {
  1649.     if (!(Opt in Options))
  1650.     return 0
  1651.     Set[Options[Opt]]
  1652.     count = Options[Opt,"count"]
  1653.     for (; count > 1; count--)
  1654.     Set[Options[Opt,count]]
  1655.     return count
  1656. }
  1657.  
  1658. # The value of each instance of option Opt that occurs in Options[] that
  1659. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1660. # Other values are made indexes of Set[].
  1661. # The return value is the number of instances of Opt in Options.
  1662. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1663.     ret = Opt2Set(Options,Opt,aSet)
  1664.     for (value in aSet)
  1665.     if (substr(value,1,1) == "!")
  1666.         nSet[substr(value,2)]
  1667.     else
  1668.         Set[value]
  1669.     return ret
  1670. }
  1671.  
  1672. # Returns true if option Opt was given on the command line.
  1673. function CmdLineOpt(Options,Opt,  i) {
  1674.     for (i = 1; (Opt,"num",i) in Options; i++)
  1675.     if (Options[Opt,"num",i] != 0)
  1676.         return 1
  1677.     return 0
  1678. }
  1679. ### End of ProcArgs library
  1680.